﻿using System;
using System.Text;

namespace Tuxedo.Compile
{
	internal static class ConstantParser
	{
		private static char ConvertCharacterEscapeCode(char backslashedValue)
		{
			switch (backslashedValue)
			{
				case 'n': return '\n';
				case 'r': return '\r';
				case 't': return '\t';
				case '0': return '\0';
				case '\'': return '\'';
				case '"': return '"';
				case '\\': return '\\';
				default: throw new Exception("Invalid backslash escape sequence: \\" + backslashedValue);
			}
		}

		private static int ParseInt(string value, int baze)
		{
			long number = ParseLong(value, baze);
			return (int)number;
		}

		private static long ParseLong(string value, int baze)
		{
			value = value.ToLowerInvariant();
			long output = 0;
			char c;
			for (int i = 0; i < value.Length; ++i)
			{
				c = value[i];
				output *= baze;
				if (baze == 16)
				{
					if (c >= '0' && c <= '9')
					{
						output += (c - '0');
					}
					else if (c >= 'a' && c <= 'f')
					{
						output += (c - 'a' + 10);
					}
					else
					{
						throw new Exception("Invalid character for hexadecimal number");
					}
				}
				else if (baze == 10)
				{
					if (c >= '0' && c <= '9')
					{
						output += (c - '0');
					}
					else
					{
						throw new Exception("Invalid character for number.");
					}
				}
				else if (baze == 8)
				{
					if (c >= '0' && c <= '7')
					{
						output += (c - '0');
					}
					else
					{
						throw new Exception("Invalid character for octal number.");
					}
				}
			}
			return output;
		}

		private static Expression TryParseLiteralString(TokenStream tokens)
		{
			if (tokens.PopIfPresent("@"))
			{
				Token stringToken = tokens.Pop();
				string stringValue = stringToken.Value;
				if (stringValue.Length < 2 || stringValue[0] != '"' || stringValue[stringValue.Length - 1] != '"')
				{
					throw new Exception("Unexpected token. Expected string.");
				}
				return new StringConstant(stringValue.Substring(1, stringValue.Length - 2));
			}
			return null;
		}

		private static Expression TryParseSimpleString(TokenStream tokens)
		{
			string value = tokens.PeekValue();
			if (value != null && value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"')
			{
				tokens.Pop();
				StringBuilder sb = new StringBuilder();
				for (int i = 1; i < value.Length - 1; ++i)
				{
					char c = value[i];
					if (c == '\\')
					{
						c = value[++i];
						if (i < value.Length - 1)
						{
							sb.Append(ConvertCharacterEscapeCode(c));
						}
						else
						{
							throw new Exception("String can't end with \\");
						}
					}
					else
					{
						sb.Append(c);
					}
				}
				return new StringConstant(sb.ToString());
			}
			return null;
		}

		private static Expression TryParseCharacterString(TokenStream tokens)
		{
			string value = tokens.PeekValue();
			if (value != null && value.Length > 2 && value[0] == '\'' && value[value.Length - 1] == '\'')
			{
				tokens.Pop();
				if (value.Length == 3)
				{
					if (value[2] == '\'')
					{
						return new CharConstant(value[1]);
					}
				}
				else if (value.Length == 4)
				{
					if (value[3] == '\'' && value[1] == '\\')
					{
						return new CharConstant(ConvertCharacterEscapeCode(value[2]));
					}
				}

				throw new Exception("Invalid character constant: " + value);
			}
			return null;
		}

		private static Expression TryParseNull(TokenStream tokens)
		{
			return tokens.PopIfPresent("null")
				? NullConstant.INSTANCE
				: null;
		}

		private static Expression TryParseBoolean(TokenStream tokens)
		{
			if (tokens.PopIfPresent("true")) return BooleanConstant.TRUE_INSTANCE;
			if (tokens.PopIfPresent("false")) return BooleanConstant.FALSE_INSTANCE;
			return null;
		}

		private const int INTEGER_TYPE = 1;
		private const int FLOAT_TYPE = 2;
		private const int LONG_TYPE = 3;
		private const int DOUBLE_TYPE = 4;

		private static Expression TryParseNumber(TokenStream tokens)
		{
			return
				ConstantParser.TryParseDoubleFloat(tokens) ?? // 123.456 | .789 | .123f
				ConstantParser.TryParseHexIntLong(tokens) ?? // 0xabcDEF123
				ConstantParser.TryParseDecimalIntLongFloat(tokens); // 01234[l|L|f|F]
		}

		private static Expression TryParseDoubleFloat(TokenStream tokens)
		{
			int tokenIndex = tokens.GetCurrentIndex();
			string value = tokens.PeekValue();
			if (value == null) return null;

			if (!tokens.PopIfPresent("."))
			{
				for (int i = 0; i < value.Length; ++i)
				{
					char c = value[i];
					if (c < '0' || c > '9')
					{
						tokens.RestoreIndex(tokenIndex);
						return null;
					}
				}
				tokens.Pop();

				if (!tokens.PopIfPresent("."))
				{
					tokens.RestoreIndex(tokenIndex);
					return null;
				}
			}
			else
			{
				value = "0";
			}

			int type = DOUBLE_TYPE;
			string postDecimalValue = tokens.PeekValue().ToLowerInvariant();
			if (postDecimalValue.Length > 1 && postDecimalValue[postDecimalValue.Length - 1] == 'f')
			{
				type = FLOAT_TYPE;
				postDecimalValue = postDecimalValue.Substring(0, postDecimalValue.Length - 1);
			}

			for (int i = 0; i < postDecimalValue.Length; ++i)
			{
				char c = postDecimalValue[i];
				if (c < '0' || c > '9')
				{
					tokens.RestoreIndex(tokenIndex);
					return null;
				}
			}

			string finalValue = value + "." + postDecimalValue;
			double numericalValue;
			if (!double.TryParse(finalValue, out numericalValue))
			{
				tokens.RestoreIndex(tokenIndex);
				return null;
			}

			tokens.Pop();

			if (type == FLOAT_TYPE)
			{
				return new FloatConstant(numericalValue);
			}
			return new DoubleConstant(numericalValue);
		}

		private static Expression TryParseDecimalIntLongFloat(TokenStream tokens)
		{
			string value = tokens.PeekValue();

			if (value == null) return null;

			int type = INTEGER_TYPE;
			if (value.Length > 1)
			{
				char suffix = value[value.Length - 1];
				if (suffix == 'l' || suffix == 'L')
				{
					type = LONG_TYPE;
					value = value.Substring(value.Length - 1);
				}
				else if (suffix == 'f' || suffix == 'F')
				{
					type = FLOAT_TYPE;
					value = value.Substring(value.Length - 1);
				}
			}

			long numericValue = 0;
			for (int i = 0; i < value.Length; ++i)
			{
				char c = value[i];
				if (c >= '0' && c <= '9')
				{
					numericValue = numericValue * 10 + (c - '0');
				}
				else
				{
					return null;
				}
			}

			if (type == INTEGER_TYPE)
			{
				tokens.Pop();
				return new IntegerConstant(numericValue);
			}
			else if (type == FLOAT_TYPE)
			{
				tokens.Pop();
				return new FloatConstant(numericValue);
			}
			else if (type == LONG_TYPE)
			{
				tokens.Pop();
				return new LongConstant(numericValue);
			}
			return null;
		}

		private static Expression TryParseHexIntLong(TokenStream tokens)
		{
			string value = tokens.PeekValue();
			if (value != null && value.Length > 2 && value[0] == '0' && value[1] == 'x')
			{
				value = value.Substring(2).ToLowerInvariant();
				int type = INTEGER_TYPE;
				if (value.Length > 3)
				{
					char suffix = value[value.Length - 1];
					if (suffix == 'l' || suffix == 'L')
					{
						type = LONG_TYPE;
						value = value.Substring(value.Length - 1);
					}
					else if (suffix == 'f' || suffix == 'F')
					{
						// This isn't a float. F is a hex character, silly
					}
				}

				long numericValue = 0;
				for (int i = 0; i < value.Length; ++i)
				{
					char c = value[i];
					if (c >= '0' && c <= '9')
					{
						numericValue = numericValue * 16 + (c - '0');
					}
					else if (c >= 'a' && c <= 'f')
					{
						numericValue = numericValue * 16 + (c - 'a' + 10);
					}
					else
					{
						return null;
					}
				}

				tokens.Pop();

				if (type == INTEGER_TYPE)
				{
					return new IntegerConstant(numericValue);
				}

				if (type == FLOAT_TYPE)
				{
					return new FloatConstant(numericValue);
				}

				if (type == LONG_TYPE)
				{
					return new LongConstant(numericValue);
				}

				throw new Exception("Invalid code path");
			}
			return null;
		}

		public static Expression Parse(TokenStream tokens)
		{
			return 
				ConstantParser.TryParseNull(tokens) ??
				ConstantParser.TryParseBoolean(tokens) ??
				ConstantParser.TryParseLiteralString(tokens) ??
				ConstantParser.TryParseSimpleString(tokens) ??
				ConstantParser.TryParseCharacterString(tokens) ??
				ConstantParser.TryParseNumber(tokens);
		}
	}
}
